package oktx;

import oktx.participant.Participant;
import oktx.utils.LruCache;
import oktx.utils.NetUtils;

import javax.transaction.Transaction;
import java.lang.management.ManagementFactory;
import java.text.SimpleDateFormat;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.Date;
import java.util.Vector;
import java.util.concurrent.atomic.AtomicLong;

/**
 * Created by huangdachao on 2018/5/14 15:50.
 */
public class TransactionContext {
    private static final LruCache<String, TransactionContext> pendingTransactionCache; // 缓存远程发起的未决JTA事务
    private static final ThreadLocal<TransactionContext> contextHolder;
    private static final String localIp;
    private static final String processId;
    private static SimpleDateFormat dateFormat;
    private static AtomicLong id;
    private static volatile int date;

    private String xid;         // 事务ID
    private boolean root;       // 本机是否事务发起者
    private Class<?> clazz;     // 事务发起方法所在类
    private String method;      // 事务发起方法
    private int depth;          // 事务嵌套层数
    private Date createdAt;     // 事务创建时间
    private Transaction transaction; // 本地JTA事物
    private Vector<Participant> participants;

    static {
        pendingTransactionCache = new LruCache<>(1000);
        contextHolder = new ThreadLocal<>();
        localIp = NetUtils.getLocalHost();
        processId = ManagementFactory.getRuntimeMXBean().getName().split("@")[0];
        dateFormat = new SimpleDateFormat("MMdd");
        id = new AtomicLong(0);
        date = LocalDate.now().getDayOfMonth();
    }

    public static TransactionContext getContext(String xid) {
        return pendingTransactionCache.get(xid);
    }

    public static TransactionContext getContext() {
        return contextHolder.get();
    }

    /**
     * 新建Context，事务为远程调用者事务的一部分，由远程调用者决定提交或回滚
     * @param xid 远程调用者分配的事务ID
     * @param transaction 本地事务对象
     * @return
     */
    public static TransactionContext newContext(String xid, Transaction transaction) {
        TransactionContext tc = new TransactionContext();
        tc.setDepth(1);
        tc.setCreatedAt(new Date());
        tc.setTransaction(transaction);
        tc.setParticipants(new Vector<>());

        if (xid != null) {
            tc.setXid(xid);
        } else {
            tc.setRoot(true);
            int now = LocalDateTime.ofInstant(tc.getCreatedAt().toInstant(), ZoneId.systemDefault()).getDayOfMonth();
            if (date != now) {
                synchronized (TransactionContext.class) {
                    if (date != now) {
                        date = now;
                        id.set(0);
                    }
                }
            }
            tc.setXid(localIp + "@" + processId + ":" + dateFormat.format(tc.getCreatedAt()) + "-" + id.incrementAndGet());
        }
        pendingTransactionCache.put(tc.getXid(), tc);
        contextHolder.set(tc);
        return tc;
    }

    /**
     * 新建Context，事务为本地发起的根事务
     * @return
     */
    public static TransactionContext newContext(Transaction transaction) {
        return newContext(null, transaction);
    }

    /**
     * 清理当前线程中的事务Cache和Context
     */
    public static void clearContext() {
        TransactionContext tc = contextHolder.get();
        if (tc != null) {
            pendingTransactionCache.remove(tc.getXid());
            contextHolder.remove();
        }
    }

    /**
     * 清理指定事务Cache和当前事务Context
     * @param xid
     */
    public static void clearContext(String xid) {
        contextHolder.remove();
        pendingTransactionCache.remove(xid);
    }

    /**
     * 清空当前事务Context
     */
    public static void resetContext() {
        contextHolder.remove();
    }

    /**
     * 继续之前的事务Context
     * @param context
     */
    public static void restoreContext(TransactionContext context) {
        contextHolder.set(context);
    }

    public String getXid() {
        return xid;
    }

    public void setXid(String xid) {
        this.xid = xid;
    }

    public boolean isRoot() {
        return root;
    }

    public void setRoot(boolean root) {
        this.root = root;
    }

    public Class<?> getClazz() {
        return clazz;
    }

    public void setClazz(Class<?> clazz) {
        this.clazz = clazz;
    }

    public String getMethod() {
        return method;
    }

    public void setMethod(String method) {
        this.method = method;
    }

    public int getDepth() {
        return depth;
    }

    public void setDepth(int depth) {
        this.depth = depth;
    }

    public Date getCreatedAt() {
        return createdAt;
    }

    public void setCreatedAt(Date createdAt) {
        this.createdAt = createdAt;
    }

    public Transaction getTransaction() {
        return transaction;
    }

    public void setTransaction(Transaction transaction) {
        this.transaction = transaction;
    }

    public Vector<Participant> getParticipants() {
        return participants;
    }

    public void setParticipants(Vector<Participant> participants) {
        this.participants = participants;
    }

    @Override
    public String toString() {
        return "TransactionContext{"
                + "xid='" + xid + '\''
                + ", clazz='" + clazz + '\''
                + ", method='" + method + '\''
                + ", depth=" + depth
                + ", createdAt=" + createdAt
                + ", participants=" + participants
                + '}';
    }
}
